﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Configuration;

namespace MediaLibraryManager
{
	public class MediaLibraryApplicationContext
	{
		private EventLog eventLog;
		private Dictionary<string, string> parameters;
		private Dictionary<string, bool> flags;
		private MediaLibraryDataContext db;
		private SourceApplication sourceApplication;
		private ConnectionStringSettings connectionString;
		private byte[] LastDeletedThumbnail { get; set; }
		private string LastDeletedThumbnailFilePath { get; set; }
		private bool LastDeletedThumbnailWasInSameDirectory { get; set; }

		public MediaLibraryApplicationContext(SourceApplication sourceApplication, bool designMode)
		{
			this.sourceApplication = sourceApplication;
			
			#region Load configuration from custom config file

			ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
			configFileMap.ExeConfigFilename = String.Format("{0}{1}",
					System.AppDomain.CurrentDomain.BaseDirectory,
					"MediaLibraryManager.config");

			if (designMode)
			{
				//TODO: Need to change to get solution path in design time
				//http://forums.asp.net/p/1066380/1544044.aspx
				configFileMap.ExeConfigFilename = @"C:\Users\Nabani\Documents\Visual Studio 2008\Projects\Media Library Manager\Media Library Manager (Class)\MediaLibraryManager.config";
			}

			Configuration config = ConfigurationManager.OpenMappedExeConfiguration(
				configFileMap,
				ConfigurationUserLevel.None);

			Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

			if (config.ConnectionStrings.ConnectionStrings.Count > 1)
			{
				appConfig.ConnectionStrings.ConnectionStrings.Add(config.ConnectionStrings.ConnectionStrings[1]);
				connectionString = config.ConnectionStrings.ConnectionStrings[1];
			}
			
			#endregion

			#region Load parameters from application configuration

			KeyValueConfigurationCollection settings = config.AppSettings.Settings;

			foreach (string item in Enum.GetNames(typeof(MediaType)))
			{
				Parameters[String.Format("{0}MonitoredPaths", item)] =
					settings[String.Format("{0}MonitoredPaths", item)].Value;
				Parameters[String.Format("{0}Extensions", item)] =
					settings[String.Format("{0}Extensions", item)].Value;
			}

			Parameters["VideoPaths"] = String.Format("{0};{1};{2};{3};{4}",
				settings["MusicVideoMonitoredPaths"].Value,
				settings["PersonalVideoMonitoredPaths"].Value,
				settings["PrivateVideoMonitoredPaths"].Value,
				settings["TVRecordingMonitoredPaths"].Value,
				settings["OtherVideoMonitoredPaths"].Value);

			Parameters["PicturePaths"] = String.Format("{0};{1}",
				settings["PictureMonitoredPaths"].Value,
				settings["PrivatePictureMonitoredPaths"].Value);

			Parameters["AudioPaths"] = settings["MusicMonitoredPaths"].Value;
			Parameters["PrivatePaths"] = settings["PrivatePaths"].Value;
			Parameters["ReplacementPaths"] = settings["ReplacementPaths"].Value;

			Parameters["TimerIntervalMilliseconds"] = settings["TimerIntervalMilliseconds"].Value;
			Parameters["WatchersInternalBufferSize"] = settings["WatchersInternalBufferSize"].Value;
			Parameters["MyMoviesRootPath"] = settings["MyMoviesRootPath"].Value;

			Parameters["ThumbnailWidth"] = settings["ThumbnailWidth"].Value;
			Parameters["ExternalVideoThumbnailsCommand"] = settings["ExternalVideoThumbnailsCommand"].Value;
			Parameters["ExternalVideoThumbnailsCommandArguments"] = settings["ExternalVideoThumbnailsCommandArguments"].Value;

			Flags["LoggingEnabled"] = settings["LoggingEnabled"].Value == "1";
			Flags["LinqLoggingEnabled"] = settings["LinqLoggingEnabled"].Value == "1";
			Flags["UpdateExistingItem"] = settings["UpdateExistingItem"].Value == "1";
			Flags["GenerateMyMoviesFileStructure"] = settings["GenerateMyMoviesFileStructure"].Value == "1";
			Flags["GenerateVideoThumbnailsFromGui"] = settings["GenerateVideoThumbnailsFromGui"].Value == "1";
			Flags["GenerateVideoThumbnailsFromService"] = settings["GenerateVideoThumbnailsFromService"].Value == "1";
			Flags["DeleteThumbnailsTogetherWithFile"] = settings["DeleteThumbnailsTogetherWithFile"].Value == "1";
			Flags["RenameThumbnailsTogetherWithFile"] = settings["RenameThumbnailsTogetherWithFile"].Value == "1";
			
			#endregion

			#region Initialize event log

			/**
				 * If you want to write entries to an event log other than the application log, set AutoLog to
				 * false, instantiate a new EventLog in the constructor for your service component, and override
				 * the OnStart, OnStop, and other command-handling methods to explicitly post entries to that log.
				 * You cannot use this EventLog instance to write to logs other than the Application log.
				 * **/

			if (!EventLog.SourceExists(sourceApplication.ToString()))
				EventLog.CreateEventSource(sourceApplication.ToString(), "Media Library Manager");

			this.eventLog = new EventLog("Media Library Manager");
			this.eventLog.Source = sourceApplication.ToString();

			#endregion
		}

		public Dictionary<string, string> Parameters
		{
			get
			{
				if (this.parameters == null)
					this.parameters = new Dictionary<string, string>();

				return this.parameters;
			}
		}

		public Dictionary<string, bool> Flags
		{
			get
			{
				if (this.flags == null)
					this.flags = new Dictionary<string, bool>();

				return this.flags;
			}
		}

		public MediaLibraryDataContext DB
		{
			get
			{
				if (this.db == null)
				{
					this.db = MediaLibraryDataContext.GetMediaLibraryDataContext(Flags["LinqLoggingEnabled"]);
					this.db.Connection.ConnectionString = this.connectionString.ConnectionString;
				}

				return this.db;
			}

			set
			{
				this.db = value;
			}
		}

		public void WriteLogEntry(string message)
		{
			if (Flags["LoggingEnabled"])
				this.eventLog.WriteEntry(message, EventLogEntryType.Information);
		}

		public void WriteLogException(Exception e, string filePath)
		{
			if (Flags["LoggingEnabled"])
			{
				if (filePath != null && filePath != string.Empty)
					this.eventLog.WriteEntry(
						String.Format("File:{0}{4}{0}{0}Message:{0}{1}{0}{0}Source: {2}{0}{0}Stack trace:{0}{3}",
							Environment.NewLine,
							e.Message,
							e.Source,
							e.StackTrace,
							filePath,
							CultureInfo.CurrentUICulture),
						EventLogEntryType.Error);
				else
					this.eventLog.WriteEntry(
						String.Format("Message:{0}{1}{0}{0}Source: {2}{0}{0}Stack trace:{0}{3}",
							Environment.NewLine,
							e.Message,
							e.Source,
							e.StackTrace,
							CultureInfo.CurrentUICulture),
						EventLogEntryType.Error);
			}
		}

		public void CreateMedia(string fileName,string fullPath,MediaType mediaType)
		{
			if (!System.Text.RegularExpressions.Regex.IsMatch(
					fileName,
					Parameters[String.Format("{0}Extensions", mediaType.ToString(), CultureInfo.CurrentUICulture)],
					System.Text.RegularExpressions.RegexOptions.IgnoreCase))
				return;

			try
			{
				// Video
				if (Parameters["VideoPaths"].Contains(fullPath.Substring(0, fullPath.LastIndexOf('\\'))))
				{
					FileInfo f = new FileInfo(fullPath);

					VideoFile vf = Video.GetVideoFile(DB, fullPath);
					bool newFile = true;

					if (vf == null)
						vf = new VideoFile();
					else if (!Flags["UpdateExistingItem"])
						return;
					else
						newFile = false;

					vf.FilePath = f.FullName;
					vf.CurrentFileName = f.Name;
					vf.FileSize = f.Length;
					vf.FileType = f.Extension.Replace(".", "");
					vf.IsDeleted = false;
					vf.IsReplacement = Parameters["ReplacementPaths"].Contains(f.DirectoryName);
					vf.IsPrivate = Parameters["PrivatePaths"].Contains(f.DirectoryName);

					if (newFile)
					{
						vf.OriginalFileName = f.Name;
						vf.VideoFileID = Guid.NewGuid();
						vf.DateAdded = f.LastWriteTime < DateTime.Now ? f.LastWriteTime : DateTime.Now;
						vf.MediaClassID = (byte)mediaType;
						db.VideoFiles.InsertOnSubmit(vf);
					}
					else
					{
						//To maintain thumbnails while moving between watched paths.
						if (Flags["RenameThumbnailsTogetherWithFile"])
						{
							if (!string.IsNullOrEmpty(this.LastDeletedThumbnailFilePath) &&
								Path.GetFileNameWithoutExtension(this.LastDeletedThumbnailFilePath) ==
								Path.GetFileNameWithoutExtension(fullPath) &&
								this.LastDeletedThumbnail != null)
							{
								System.Data.Linq.EntitySet<Video> videos = vf.Videos;
								string thumbnailFilePath = string.Empty;

								if (this.LastDeletedThumbnailWasInSameDirectory)
									thumbnailFilePath = 
										Path.Combine(
											Path.GetDirectoryName(fullPath),
											Path.GetFileName(this.LastDeletedThumbnailFilePath));
								else
									thumbnailFilePath = this.LastDeletedThumbnailFilePath;

								foreach (Video v in videos)
								{
									v.ThumbnailFilePath = thumbnailFilePath;
								}
								
								File.WriteAllBytes(thumbnailFilePath,this.LastDeletedThumbnail);
							}
						}
					}

					DB.SubmitChanges();

					if (Flags["GenerateMyMoviesFileStructure"])
					{
						Video v = Video.GetVideo(DB, vf.VideoFileID);
						v.GenerateMyMoviesFileSystemStructure(
							String.Format("{0}{1}{2}",
								Parameters["MyMoviesRootPath"],
								Parameters["MyMoviesRootPath"].EndsWith(@"\") ? "" : @"\",
								mediaType.ToString()));
						v = null;
					}

					if ((
							this.sourceApplication == SourceApplication.MediaLibraryManager &&
							Flags["GenerateVideoThumbnailsFromGui"] &&
							Parameters["ExternalVideoThumbnailsCommand"] != string.Empty
						) ||
						(
							this.sourceApplication == SourceApplication.WatcherService &&
							Flags["GenerateVideoThumbnailsFromService"] &&
							Parameters["ExternalVideoThumbnailsCommand"] != string.Empty
						))
					{
						ProcessStartInfo processInfo = new ProcessStartInfo();
						processInfo.FileName = Parameters["ExternalVideoThumbnailsCommand"];
						processInfo.CreateNoWindow = true;
						processInfo.UseShellExecute = false;
						processInfo.Arguments = String.Format(Parameters["ExternalVideoThumbnailsCommandArguments"],
							fullPath);
						Process.Start(processInfo);

						System.Data.Linq.EntitySet<Video> videos = vf.Videos;

						foreach (Video v in videos)
						{
							v.ThumbnailFilePath = fullPath.Replace(
								String.Format(".{0}", vf.FileType),
								".jpg");
						}

						DB.SubmitChanges();
						videos = null;
					}

					f = null;

					WriteLogEntry(
						String.Format("{0} {1}: {2}",
							mediaType,
							newFile ? "created" : "updated",
							fullPath,
							CultureInfo.CurrentUICulture));
				}
			}
			catch (Exception ex)
			{
				DB = null;
				WriteLogException(ex, fullPath);
			}
		}

		public void DeleteMedia(string fileName, string fullPath, MediaType mediaType)
		{
			if (!System.Text.RegularExpressions.Regex.IsMatch(
					fileName,
					Parameters[String.Format("{0}Extensions", mediaType.ToString(), CultureInfo.CurrentUICulture)],
					System.Text.RegularExpressions.RegexOptions.IgnoreCase))
				return;

			try
			{
				// Video
				if (Parameters["VideoPaths"].Contains(fullPath.Substring(0, fullPath.LastIndexOf('\\'))))
				{
					VideoFile vf = Video.GetVideoFile(DB, fullPath);

					if (vf != null)
					{
						if (Flags["GenerateMyMoviesFileStructure"])
						{
							Video v = Video.GetVideo(DB, vf.VideoFileID);
							v.DeleteMyMoviesFileSystemStructure(
								String.Format("{0}{1}{2}",
									Parameters["MyMoviesRootPath"],
									Parameters["MyMoviesRootPath"].EndsWith(@"\") ? "" : @"\",
									mediaType.ToString()));
							v = null;
						}

						if (Flags["DeleteThumbnailsTogetherWithFile"])
						{
							System.Data.Linq.EntitySet<Video> videos = vf.Videos;

							foreach (Video v in videos)
							{
								if (!string.IsNullOrEmpty(v.ThumbnailFilePath))
								{
									LastDeletedThumbnail = null;
									LastDeletedThumbnailFilePath = string.Empty;
									LastDeletedThumbnailWasInSameDirectory = false;

									if (File.Exists(v.ThumbnailFilePath))
									{
										LastDeletedThumbnail = File.ReadAllBytes(v.ThumbnailFilePath);
										LastDeletedThumbnailFilePath = v.ThumbnailFilePath;
										LastDeletedThumbnailWasInSameDirectory =
											Path.GetDirectoryName(v.ThumbnailFilePath) ==
											Path.GetDirectoryName(fullPath);
										File.Delete(v.ThumbnailFilePath);
									}

									v.ThumbnailFilePath = null;
									v.ThumbnailPosition = null;
								}
							}
						}

						vf.IsDeleted = true;
						DB.SubmitChanges();

						WriteLogEntry(String.Format("{0} deleted: {1}", mediaType, fullPath));
					}
				}
			}
			catch (Exception ex)
			{
				DB = null;
				WriteLogException(ex, fullPath);
			}
		}

		public void RenameMedia(string fileName,string oldFullPath,string fullPath,MediaType mediaType)
		{
			if (!System.Text.RegularExpressions.Regex.IsMatch(
					fileName,
					Parameters[String.Format("{0}Extensions", mediaType.ToString(), CultureInfo.CurrentUICulture)],
					System.Text.RegularExpressions.RegexOptions.IgnoreCase))
				return;

			try
			{
				// Video
				if (Parameters["VideoPaths"].Contains(fullPath.Substring(0, fullPath.LastIndexOf('\\'))))
				{
					VideoFile vf = Video.GetVideoFile(DB, oldFullPath);
					
					if (vf != null)
					{
						if (Flags["RenameThumbnailsTogetherWithFile"])
						{
							System.Data.Linq.EntitySet<Video> videos = vf.Videos;

							foreach (Video v in videos)
							{
								if (!string.IsNullOrEmpty(v.ThumbnailFilePath) &&
									File.Exists(v.ThumbnailFilePath) &&
									//Check if thumbnail has the same name of the file, if not, it could
									//mean the file has more than one video associated, don't rename in this
									//case.
									Path.GetFileName(v.ThumbnailFilePath) == Path.GetFileName(oldFullPath))
								{
									File.Move(
											v.ThumbnailFilePath,
											fullPath.Replace(
												String.Format(".{0}", vf.FileType),
												".jpg"));

									v.ThumbnailFilePath =
										fullPath.Replace(
											String.Format(".{0}", vf.FileType),
											".jpg");
								}
							}
						}

						vf.FilePath = fullPath;
						DB.SubmitChanges();

						if (Flags["GenerateMyMoviesFileStructure"])
						{
							Video v = Video.GetVideo(DB, vf.VideoFileID);
							v.DeleteMyMoviesFileSystemStructure(
								String.Format("{0}{1}{2}",
									Parameters["MyMoviesRootPath"],
									Parameters["MyMoviesRootPath"].EndsWith(@"\") ? "" : @"\",
									mediaType.ToString()));
							v = null;
						}

						WriteLogEntry(
							String.Format("{0} renamed from {1} to {2}",
								mediaType,
								oldFullPath,
								fullPath,
								CultureInfo.CurrentUICulture));
					}
				}
			}
			catch (Exception ex)
			{
				DB = null;
				WriteLogException(ex, fullPath);
			}
		}
	}
}
